import {
  FindManyOptions,
  FindOptionsWhere,
  ObjectLiteral,
  Repository,
  SelectQueryBuilder,
} from 'typeorm'

import { createStandardPaginationObject, StandardPagination } from './standard-pagination'
import { IPaginationOptions, PaginationTypeEnum } from './interface'

const DEFAULT_LIMIT = 10
const DEFAULT_PAGE = 1

function resolveOptions(
  options: IPaginationOptions,
): [number, number, PaginationTypeEnum] {
  const { page, pageSize, paginationType } = options

  return [
    page || DEFAULT_PAGE,
    pageSize || DEFAULT_LIMIT,
    paginationType || PaginationTypeEnum.TAKE_AND_SKIP,
  ]
}

async function standardPaginateRepository<T>(
  repository: Repository<T>,
  options: IPaginationOptions,
  searchOptions?: FindOptionsWhere<T> | FindManyOptions<T>,
): Promise<StandardPagination<T>> {
  const [page, limit] = resolveOptions(options)

  const promises: [Promise<T[]>, Promise<number>] = [
    repository.find({
      skip: limit * (page - 1),
      take: limit,
      ...searchOptions,
    }),
    repository.count(searchOptions as FindOptionsWhere<T>),
  ]

  const [items, total] = await Promise.all(promises)

  return createStandardPaginationObject<T>({
    items,
    totalItems: total,
    currentPage: page,
    limit,
  })
}

async function standardPaginateQueryBuilder<T>(
  queryBuilder: SelectQueryBuilder<T>,
  options: IPaginationOptions,
): Promise<StandardPagination<T>> {
  const [page, limit, paginationType] = resolveOptions(options)

  if (paginationType === PaginationTypeEnum.TAKE_AND_SKIP)
    queryBuilder.take(limit).skip((page - 1) * limit)
  else
    queryBuilder.limit(limit).offset((page - 1) * limit)

  const [items, total] = await queryBuilder.getManyAndCount()

  return createStandardPaginationObject<T>({
    items,
    totalItems: total,
    currentPage: page,
    limit,
  })
}

export async function standardPaginateRaw<T>(
  queryBuilder: SelectQueryBuilder<T>,
  options: IPaginationOptions,
): Promise<StandardPagination<T>> {
  const [page, limit, paginationType] = resolveOptions(options)

  const promises: [Promise<T[]>, Promise<number>] = [
    (paginationType === PaginationTypeEnum.LIMIT_AND_OFFSET
      ? queryBuilder.limit(limit).offset((page - 1) * limit)
      : queryBuilder.take(limit).skip((page - 1) * limit)
    ).getRawMany<T>(),
    queryBuilder.getCount(),
  ]

  const [items, total] = await Promise.all(promises)

  return createStandardPaginationObject<T>({
    items,
    totalItems: total,
    currentPage: page,
    limit,
  })
}

/**
 * 标准分页函数 - 返回格式: {data: [], page: 1, pageSize: 10, total: 100, totalPage: 10}
 */
export async function standardPaginate<T extends ObjectLiteral>(
  repository: Repository<T>,
  options: IPaginationOptions,
  searchOptions?: FindOptionsWhere<T> | FindManyOptions<T>,
): Promise<StandardPagination<T>>
export async function standardPaginate<T>(
  queryBuilder: SelectQueryBuilder<T>,
  options: IPaginationOptions,
): Promise<StandardPagination<T>>

export async function standardPaginate<T extends ObjectLiteral>(
  repositoryOrQueryBuilder: Repository<T> | SelectQueryBuilder<T>,
  options: IPaginationOptions,
  searchOptions?: FindOptionsWhere<T> | FindManyOptions<T>,
) {
  return repositoryOrQueryBuilder instanceof Repository
    ? standardPaginateRepository<T>(repositoryOrQueryBuilder, options, searchOptions)
    : standardPaginateQueryBuilder<T>(repositoryOrQueryBuilder, options)
}